
#ifndef _VARARRAY_HPP_
#define _VARARRAY_HPP_

#include <initializer_list>
#include <cassert>
#include <cstring>
#include <array>
#include <type_traits>
#include <string>
// FORTRAN 格式的索引是 列优先 如 a(2,3,4) 为2行3列4页
// 即一列有两个元素，一行有3个元素  页有四个（页）

// c 和 fortran 格式混搭了  
// 除了Freconstruct 其余 c 均可用 只有先声明，后设置(用 Freconstruct 设置Fortran格式)

// c 格式为 2页 3行 四列  注意区分 行优先
// #define _FORTRAN_INDEX_
#define _C_INDEX_

/**
 * @brief 
 *   表达式模板有待添加
 * @tparam T 
 * @tparam D 
 */
template <typename T, int D>
class VarArray
{

  public:
    typedef int index_t;
    typedef T *iter;
    typedef const T *const_iter;
    typedef T value_type;
    typedef T &reference;
    typedef const T &const_reference;
    // using iter = T *;
    // using const_iter = const T *;
    // using value_type = T;

    // 构造函数和拷贝函数
    VarArray() = default;
    VarArray(index_t i, index_t j = 0,
             index_t k = 0, index_t m = 0);
    // VarArray(const std::string str1, const std::string str2 = "",
    //  const std::string str3 = "", const std::string str4 = "");
    VarArray(std::initializer_list<index_t> dimList);
    VarArray(const VarArray &src);
    VarArray(VarArray &&src);
    VarArray &operator=(const VarArray &src);
    VarArray &operator=(VarArray &&src);
    ~VarArray() { delete[] elem; }

    // 获取相应维度， 最后一个维度变化最快 c 模式   0 1 2 3
    template <int DD>
    int Dim() const
    {
        static_assert(DD < D, "exceed the dimension you have defined\n");
#ifdef _C_INDEX_
        return dim[DD];
#endif
#ifdef _FORTRAN_INDEX_
        return dim[DD * 2 + 1];
#endif
    }
    // 0 1 2 3
    int Dim(int i) const
    {
        assert(i < D && -1 < i);
#ifdef _C_INDEX_
        return dim[i];
#endif
#ifdef _FORTRAN_INDEX_
        return dim[i * 2 + 1];
#endif
    }
    // fortran 格式取值
    T &operator()(index_t i) noexcept
    {
        static_assert(D == 1, "one index, one dimension");
#ifdef _C_INDEX_
        assert(-1 < i && i < dim[0]);
        return elem[i];
#endif
#ifdef _FORTRAN_INDEX_
        assert(dim[0] <= i && i <= (dim[1] + dim[0] - 1));
        return elem[i - dim[0]];
#endif
    }

    T &operator()(index_t i, index_t j) noexcept
    {
        static_assert(D == 2, "two index, two dimension");
#ifdef _C_INDEX_
        assert(-1 < i && i < dim[0] && -1 < j && j < dim[1]);
        return elem[i * dim[1] + j];
#else
        assert(dim[0] <= i && i <= (dim[1] + dim[0] - 1) &&
               dim[2] <= j && j <= (dim[3] + dim[2] - 1));
        return elem[(j - dim[2]) * dim[1] + i - dim[0]];
#endif
    }
    T &operator()(index_t i, index_t j, index_t k) noexcept
    {
        static_assert(D == 3, "three index, three dimension");

#ifdef _C_INDEX_
        assert(-1 < i && i < dim[0] && -1 < j && j < dim[1] &&
               -1 < k && k < dim[2]);
        return elem[(i * dim[1] + j) * dim[2] + k];
#else
        assert(dim[0] <= i && i <= (dim[1] + dim[0] - 1) &&
               dim[2] <= j && j <= (dim[3] + dim[2] - 1) &&
               dim[4] <= k && k <= (dim[5] + dim[4] - 1));
        return elem[((k - dim[4]) * dim[3] + j - dim[2]) * dim[1] + i - dim[0]];
        // return elem[(k * dim[1] + j) * dim[0] + i];
#endif
    }

    T &operator()(index_t i, index_t j, index_t k, index_t m) noexcept
    {
        static_assert(D == 4, "four index, four dimension");

#ifdef _C_INDEX_
        assert(-1 < i && i < dim[0] && -1 < j && j < dim[1] &&
               -1 < k && k < dim[2] && -1 < m && m < dim[3]);
        return elem[((i * dim[1] + j) * dim[2] + k) * dim[3] + m];
#else
        assert(dim[0] <= i && i <= (dim[1] + dim[0] - 1) &&
               dim[2] <= j && j <= (dim[3] + dim[2] - 1) &&
               dim[4] <= k && k <= (dim[5] + dim[4] - 1) &&
               dim[6] <= m && m <= (dim[7] + dim[6] - 1));
        return elem[(((m - dim[6]) * dim[5] + k - dim[4]) * dim[3] + j - dim[2]) * dim[1] + i - dim[0]];
#endif
    }
    inline const_reference operator()(index_t i)
        const noexcept;
    inline const_reference operator()(index_t i, index_t j)
        const noexcept;
    inline const_reference operator()(index_t i, index_t j,
                                      index_t k)
        const noexcept;
    inline const_reference operator()(index_t i, index_t j,
                                      index_t k, index_t m)
        const noexcept;

    // 重新分配内存和各维度大小  这个函数使用不频繁，可以简写
    inline bool ReConstruct(int i) noexcept;
    inline bool ReConstruct(int i, int j) noexcept;
    inline bool ReConstruct(int i, int j, int k) noexcept;
    inline bool ReConstruct(int i, int j, int k, int m) noexcept;
#ifdef _FORTRAN_INDEX_
    // 不嫌麻烦的话可以写类似上面的四个
    bool FReConstruct(int i1, int i2,
                      int j1 = 0, int j2 = 0,
                      int k1 = 0, int k2 = 0,
                      int m1 = 0, int m2 = 0);
#endif
    // 跟复制函数是不同的，复制函数会清空原来的内存
    inline void CopyValueFrom(const VarArray &src) noexcept;
    inline void MoveValueFrom(VarArray &&src) noexcept;
    inline VarArray &AssignValue(T val) noexcept;
    inline VarArray &operator=(double d) noexcept;

    inline VarArray &operator*=(double fac) noexcept;
    inline VarArray &operator/=(double fac) noexcept;
    inline VarArray &operator+=(double fac) noexcept;
    inline VarArray &operator-=(double fac) noexcept;

    iter begin() noexcept { return elem; }
    const_iter begin() const noexcept { return elem; }
    iter end() noexcept { return elem + sz; }
    const_iter end() const noexcept { return elem + sz; }
    index_t size() const noexcept { return sz; }
    iter data() noexcept { return elem; }
    const_iter data() const noexcept { return elem; }

  private:
// 默认从0开始
#ifdef _FORTRAN_INDEX_
    std::array<int, 2 * D> dim{};
#else
    std::array<int, D> dim{};
#endif
    T *elem{};
    index_t sz{};

    void copy_from(const VarArray &src);
    void move_from(VarArray &src);
};

template <typename T, int D>
void VarArray<T, D>::copy_from(const VarArray &src)
{
    dim = src.dim;
    sz = src.sz;
    elem = new T[sz];
    assert(elem);
    memcpy(elem, src.elem, sizeof(T) * sz);
}

template <typename T, int D>
void VarArray<T, D>::move_from(VarArray &src)
{
    dim = src.dim;
    sz = src.sz;
    elem = src.elem;
    src.sz = 0;
    src.elem = nullptr;
    src.dim[0] = 0;
}
/**
 * @brief Construct a new Var Array< T,  D>:: Var Array object
 *      唯一的可以指定维度初始化
 * @tparam T 
 * @tparam D 
 * @param dimList 
 */
template <typename T, int D>
VarArray<T, D>::VarArray(std::initializer_list<index_t> dimList)
{
    index_t size = dimList.size();
    assert(D == size);
    const index_t *beg = dimList.begin();
    index_t tmp{1};
    for (index_t i = 0; i < size; ++i)
    {
        dim[i] = *beg++;
        tmp *= dim[i];
    }
    sz = tmp;
    elem = new T[sz];
    assert(elem);
}
// 拷贝构造函数
template <typename T, int D>
VarArray<T, D>::VarArray(const VarArray &src)
{
    copy_from(src);
}
// 移动构造函数
template <typename T, int D>
VarArray<T, D>::VarArray(VarArray &&src)
{
    move_from(src);
}
template <typename T, int D>
VarArray<T, D>::VarArray(index_t i, index_t j,
                         index_t k, index_t m)
{
    sz = 1;
    // 这样写的话判断条件没有那么严格了
    switch (D)
    {
    case 4:
        assert(m > 0);
        dim[3] = m;
        sz = sz * m;
    case 3:
        assert(k > 0);
        dim[2] = k;
        sz = sz * k;
    case 2:
        assert(j > 0);
        dim[1] = j;
        sz = sz * j;
    case 1:
        dim[0] = i;
        sz = sz * i;
        break;
    default:
        assert(D < 5 && 0 < D);
        break;
    }
    elem = new T[sz];
}

#ifdef _FORTRAN_INDEX_
template <typename T, int D>
bool VarArray<T, D>::FReConstruct(int i1, int i2, int j1, int j2,
                                  int k1, int k2, int m1, int m2)
{
    assert(i1 <= i2 && j1 <= j2 && k1 <= k2 && m1 <= m2);
    switch (D)
    {
        // 起点和跨距
    case 4:
        dim[6] = m1;
        dim[7] = m2 - m1 + 1;
    case 3:
        dim[4] = k1;
        dim[5] = k2 - k1 + 1;
    case 2:
        dim[2] = j1;
        dim[3] = j2 - j1 + 1;
    case 1:
        dim[0] = i1;
        dim[1] = i2 - i1 + 1;
        break;
    // 下面为错误的情况
    default:
        assert(0 < D && D < 5);
        break;
    }
    sz = dim[1] * dim[3] * dim[5] * dim[7];
    return elem = new T[sz];
}
#endif

// 赋值运算符
template <typename T, int D>
VarArray<T, D> &VarArray<T, D>::operator=(const VarArray &src)
{
    if (this == &src)
        return src;
    delete[] elem;
    copy_from(src);
    return *this;
}
// 移动赋值运算符
template <typename T, int D>
VarArray<T, D> &VarArray<T, D>::operator=(VarArray &&src)
{
    if (this == &src)
        return src;
    delete[] elem;
    move_from(src);
}

template <typename T, int D>
const T &VarArray<T, D>::operator()(index_t i) const noexcept
{
    static_assert(D == 1, "one index, one dimension");
    assert(-1 < i && i < dim[0]);
    return elem[i];
}
template <typename T, int D>
const T &VarArray<T, D>::operator()(index_t i, index_t j)
    const noexcept
{
    static_assert(D == 2, "two index, two dimension");
    assert(-1 < i && i < dim[0] && -1 < j && j < dim[1]);
#ifdef _C_INDEX_
    return elem[i * dim[1] + j];
#else
    return elem[j * dim[0] + i];
#endif
}
template <typename T, int D>
const T &VarArray<T, D>::operator()(index_t i, index_t j,
                                    index_t k) const noexcept
{
    static_assert(D == 3, "three index, three dimension");
    assert(-1 < i && i < dim[0] && -1 < j && j < dim[1] &&
           -1 < k && k < dim[2]);
#ifdef _C_INDEX_
    return elem[(i * dim[1] + j) * dim[2] + k];
#else
    return elem[(k * dim[1] + j) * dim[0] + i];
#endif
}
template <typename T, int D>
const T &VarArray<T, D>::operator()(
    index_t i, index_t j, index_t k, index_t m) const noexcept
{
    static_assert(D == 4, "four index, four dimension");
    assert(-1 < i && i < dim[0] && -1 < j && j < dim[1] &&
           -1 < k && k < dim[2] && -1 < m && m < dim[3]);
#ifdef _C_INDEX_
    return elem[((i * dim[1] + j) * dim[2] + k) * dim[3] + m];
#else
    return elem[((m * dim[2] + k) * dim[1] + j) * dim[0] + i];
#endif
}
template <typename T, int D>
bool VarArray<T, D>::ReConstruct(int i) noexcept
{
    static_assert(D == 1, "not match the dimension");
    delete[] elem;
    sz = dim[0] = i;
    return elem = new T[i];
}
template <typename T, int D>
bool VarArray<T, D>::ReConstruct(int i, int j) noexcept
{
    static_assert(D == 2, "not match the dimension");
    delete[] elem;
    dim[0] = i;
    dim[1] = j;
    sz = i * j;
    return elem = new T[sz];
}
template <typename T, int D>
bool VarArray<T, D>::ReConstruct(int i, int j, int k) noexcept
{
    static_assert(D == 3, "not match the dimension");
    delete[] elem;
    dim[0] = i;
    dim[1] = j;
    dim[2] = k;
    sz = i * j * k;
    return elem = new T[sz];
}
template <typename T, int D>
bool VarArray<T, D>::ReConstruct(int i, int j, int k, int m) noexcept
{
    static_assert(D == 4, "not match the dimension");
    delete[] elem;
    dim[0] = i;
    dim[1] = j;
    dim[2] = k;
    dim[3] = m;
    sz = i * j * k * m;
    return elem = new T[sz];
}

template <typename T, int D>
void VarArray<T, D>::CopyValueFrom(const VarArray &src) noexcept
{
    assert(dim == src.dim);
    memcpy(elem, src.elem, sizeof(T) * sz);
}
template <typename T, int D>
void VarArray<T, D>::MoveValueFrom(VarArray &&src) noexcept
{
    assert(dim == src.dim);
    delete[] elem;
    elem = src.elem;
    sz = src.dim[0] = 0;
}
template <typename T, int D>
VarArray<T, D> &VarArray<T, D>::operator*=(double fac) noexcept
{
    for (index_t i = 0; i < sz; ++i)
        elem[i] *= fac;
    return *this;
}
template <typename T, int D>
VarArray<T, D> &VarArray<T, D>::operator/=(double fac) noexcept
{
    for (index_t i = 0; i < sz; ++i)
        elem[i] /= fac;
    return *this;
}
template <typename T, int D>
VarArray<T, D> &VarArray<T, D>::operator+=(double fac) noexcept
{
    for (index_t i = 0; i < sz; ++i)
        elem[i] += fac;
    return *this;
}
template <typename T, int D>
VarArray<T, D> &VarArray<T, D>::operator-=(double fac) noexcept
{
    for (index_t i = 0; i < sz; ++i)
        elem[i] -= fac;
    return *this;
}
template <typename T, int D>
VarArray<T, D> &VarArray<T, D>::AssignValue(T val) noexcept
{
    for (index_t i = 0; i < sz; ++i)
        elem[i] = val;
    return *this;
}

template <typename T, int D>
VarArray<T, D> &VarArray<T, D>::operator=(double d) noexcept
{
    for (index_t i = 0; i < sz; ++i)
        elem[i] = d;
    return *this;
}

#endif